#ifndef CONTAINER_H
#define CONTAINER_H

// Add elements to a growing array
//      The element defines a vertex array and the bounding box in this first phase
// Compile the container
//      The vertex array is being transferred to a linear array and released from the element
//      The element mutates in an assigned state, gets an index to it's vertex data, index data, the size of the vertices and indices

#include <QtCore>

#include "element.h"
#include "vecmath.h"
#include "camera.h"
#include "vertexbufferobject.h"
#include "ray.h"

class PartitionTreeNode
{
public:
    PartitionTreeNode(float start = 0.0f, float end = 0.0f);
    ~PartitionTreeNode();
    
    void split(const QVector<Element*>& elements);
    void updateIndices(unsigned prevIndexEnd = 0);
    
    void setLeftChildNode(PartitionTreeNode* leftChildNode) { this->leftChildNode = leftChildNode; leftChildNode->parent = this; }
    void setRightChildNode(PartitionTreeNode* rightChildNode) { this->rightChildNode = rightChildNode; rightChildNode->parent = this; }
    
    bool isLeaf() const { return (leftChildNode == NULL && rightChildNode == NULL); }
    PartitionTreeNode* getLeaf(float position);
    QVector<PartitionTreeNode*> getLeaves();
	QVector<PartitionTreeNode*> getLeaves(PartitionTreeNode* includedEndNode, PartitionTreeNode* previous, bool& done); // Collect from *this (leaf) to includedEndNode (leaf)

protected:
    float getSplitPosition() const { return ((start + end) / 2.0f); }
    
public:
    float start, end;
    PartitionTreeNode* parent;
    PartitionTreeNode* leftChildNode;
    PartitionTreeNode* rightChildNode;
    
    unsigned indexStart, indexEnd;
    Element** elements;
    unsigned elementsSize;
    
protected:
    static unsigned minimalElementCountForSplit;
};

class PickContainer
{
public:
    PickContainer();
	
	const QString& getName() const { return name; }
	void setName(const QString& name) { this->name = name; }
	
	bool isLabeled() const { return labeled; }
	void setLabeled(bool flag) { labeled = flag; }
	
	bool isSelected() const { return selected; }
	void setSelected(bool flag) { selected = flag; }

    void getPosition(Vec3f out) const { SetVec3f(out, position); }
    void setPosition(Vec3f position) { SetVec3f(this->position, position); }

    void getScaling(Vec3f out) const { SetVec3f(out, scaling); }
    void setScaling(Vec3f scaling) { SetVec3f(this->scaling, scaling); }
    
    void worldToContainerMat4f(Mat4f out) const;
    void worldToContainerDirectionMat4f(Mat4f out) const;
    void containerToWorldMat4f(Mat4f out) const;
    void containerToWorldDirectionMat4f(Mat4f out) const;

    void addElement(Element* element);
    QVector<Element*> pickElementsWithRay(const Ray& ray) const;
	bool hit(const Ray& ray) const;
	
	void setLength(float newLength) { boundingBox.setWidth(newLength); }
	
    void setElementSelected(Element* element, bool selected = true) { if (selected) selectedElements.insert(element); else selectedElements.remove(element); }
    void toggleElementSelected(Element* element) { setElementSelected(element, !selectedElements.contains(element)); }
    void singleElementSelected(Element* element) { selectedElements.clear(); selectedElements.insert(element); }

	void layoutWithCamera(Camera& camera);
	
	const QVector<Element*>& getElements() const { return elements; }
	
	QList<Element*> getSelection() const;
	bool clearSelection() { bool cleared = selectedElements.size() > 0; selectedElements.clear(); return cleared; }

protected:
    Ray getContainerRelativeRay(const Ray& ray) const;

public:
    BoundingBox boundingBox;
	BoundingBox labelBoundingBox;
	BoundingBox labeledBoundingBox;

protected:
	QString name;
	bool labeled;
	bool selected;
	
    Vec3f position;
    Vec3f scaling;
    
    QVector<Element*> elements;

    QSet<Element*> selectedElements;
};

class Container : public PickContainer
{
public:
    Container();
    ~Container();
    
    void compile();
    void setupForDrawing(bool deleteVertexArray = false) { if (!compiledElements) return; compiledElements->generate(); compiledElements->send(deleteVertexArray); modified = false; }
	void reassignColors();
    
    void addElement(Element* element);
    QVector<Element*> pickElementsWithRay(const Ray& ray) const;
    
    void drawVBOWithCamera(Camera& camera);
	void drawBackgroundWithCamera(Camera& camera);
    void drawVAWithCamera(Camera& camera) const;
    void drawTextWithCamera(Camera& camera) const;
    
	QVector<Element*> leftClippedElements(Camera& camera, unsigned maxSize, bool& hitsFirst) const;
	QVector<Element*> rightClippedElements(Camera& camera, unsigned maxSize, bool& hitsFirst) const;

    PackedVecColor* getElementVertices(Element* element) const;
    GLuint* getElementIndices(Element* element) const;
    
protected:
    VertexBufferObject* compiledElements;
	bool modified;
    
    PartitionTreeNode* root;
    unsigned elementsVerticesSize;
    unsigned elementsIndicesSize;
};

#endif // CONTAINER_H
